package v1_simple_rpc

import (
	"context"
	"encoding/json"
	"errors"
	"github.com/silenceper/pool"
	"net"
	"reflect"
	"time"
)

// Client 客户端:
//	• 初始化代理
//	• 代理会利用反射获得调用信息
//	• 将调用信息编码成字节流，加上长度字段 • 将数据发送到服务端
//	• 等待并且解析响应
// 客户端和服务端是对称的
type Client struct {
	pool pool.Pool
}

// Invoke 客户端的代理实现功能：
// 		负责将请求 Request 编码成二进制流然后发生到服务端，并将服务端的处理结果写入 Response.data中
// 		Request 中携带了调用信息：服务名;方法名;方法调用所需参数
// 		Response 为响应结果
func (c *Client) Invoke(ctx context.Context, req *Request) (*Response, error) {
	reqBytes, err := json.Marshal(req)
	if err != nil {
		return nil, err
	}
	respBytes, err := c.handleMsg(reqBytes)
	if err != nil {
		return nil, err
	}
	return &Response{Data: respBytes}, nil
}

func (c *Client) handleMsg(data []byte) ([]byte, error) {
	val, err := c.pool.Get()
	if err != nil {
		return nil, err
	}
	conn := val.(net.Conn)
	defer func() {
		_ = conn.Close()
	}()
	req := EncodeMsg(data)
	_, err = conn.Write(req)
	if err != nil {
		return nil, err
	}
	return ReadMsg(conn)
}

func NewClient(addr string) (*Client, error) {
	p, err := pool.NewChannelPool(&pool.Config{
		InitialCap:  1,
		MaxCap:      30,
		MaxIdle:     10,
		IdleTimeout: time.Minute,
		Factory: func() (interface{}, error) {
			return net.DialTimeout("tcp", addr, time.Second*3)
		},
		Close: func(i interface{}) error {
			return i.(net.Conn).Close()
		},
	})
	if err != nil {
		return nil, err
	}
	return &Client{
		pool: p,
	}, nil
}

// InitClientProxy 通过反射实现为 service 的 GetById 之类的函数类型的字段"赋值"
// 这些函数类型的字段完成的功能都是：
// 		1.先反射还原调用信息参数
//		2.然后才是真正的向服务端发起请求，并接收响应
//		3.反序列化服务端的字节结果
func InitClientProxy(serverAddr string, service Service) error {
	client, err := NewClient(serverAddr)
	if err != nil {
		return err
	}
	return setFuncField(service, client)
}

// 给 Service 的 func 字段逐一赋值
func setFuncField(service Service, proxy Proxy) error {
	if service == nil {
		return errors.New("rpc: 不支持 nil")
	}
	val := reflect.ValueOf(service)
	typ := val.Type()
	// 只支持指向结构体的一级指针
	if typ.Kind() != reflect.Pointer || typ.Elem().Kind() != reflect.Struct {
		return errors.New("rpc: 只支持指向结构体的一级指针")
	}

	val = val.Elem()
	typ = typ.Elem()

	numField := typ.NumField()
	for i := 0; i < numField; i++ {
		fieldVal := val.Field(i)
		fieldTyp := typ.Field(i)
		if fieldVal.CanSet() { // 通过反射进行赋值
			// 先反射创建方法的实现
			fn := func(args []reflect.Value) (results []reflect.Value) {
				// 1.先反射还原调用信息参数
				ctx := args[0].Interface().(context.Context)
				retVal := reflect.New(fieldTyp.Type.Out(0).Elem())
				reflectNilErr := reflect.Zero(reflect.TypeOf(new(error)).Elem())
				reqBytes, err := json.Marshal(args[1].Interface())
				if err != nil {
					return []reflect.Value{retVal, reflect.ValueOf(err)}
				}
				req := &Request{
					ServiceName: service.Name(),
					MethodName:  fieldTyp.Name,
					Arg:         reqBytes,
				}
				// 2.然后才是真正的向服务端发起请求，并接收响应
				response, err := proxy.Invoke(ctx, req)
				if err != nil {
					return []reflect.Value{retVal, reflect.ValueOf(err)}
				}
				// 3.反序列化服务端的字节结果
				err = json.Unmarshal(response.Data, retVal.Interface())
				if err != nil {
					return []reflect.Value{retVal, reflect.ValueOf(err)}
				}
				return []reflect.Value{retVal, reflectNilErr}
			}
			fnVal := reflect.MakeFunc(fieldTyp.Type, fn)
			fieldVal.Set(fnVal) // 反射进行赋值
		}
	}
	return nil
}
